//! Tick executor logic.

use std::collections::*;
use std::sync::Arc;
use std::thread;
use std::time;

use craftdig_datamodel::block::{action, msgs, BlkId, BlockStateSnapshot};
use craftdig_datamodel::entity::{EntityId, EntityState, PhysicsComponent};
use craftdig_datamodel::message::{BlockContext, MessageContainer, MsgTyId};
use craftdig_datamodel::player::model::make_player_aabb;
use craftdig_datamodel::world::action::{PlayerInput, TickInput};
use craftdig_datamodel::world::{editable, history, WorldState, WorldView};
use craftdig_math::*;

use crate::phys::compute_entity_physics_update;
use crate::phys::EntityPhysState;
use crate::tick::update::EntityUpdateData;

use super::config::SimConfig;
use super::control::*;
use super::error::*;
use super::state::*;
use super::update::BlockChange;

/// Applies changes that were deferred from the previous tick into this tick.
fn apply_prev_next_tick(state: &mut TickState<'_>, prev_next: NextTickState) -> Result<(), Error> {
    for chg in prev_next.block_changes() {
        let pos = chg.target_pos();
        eprintln!("craftdig: carrying over block change at {pos:?}");
        state.queue_set_block_state(chg.target_pos(), chg.new_state());
    }

    // Now reinsert any stale messages into the current tick.
    let prev_tick = prev_next.msgs();
    for (blk, mc) in prev_tick.iter_messages() {
        // FIXME avoid clone
        eprintln!("craftdig: carrying over block msg to {blk:?}");
        state.queue_immediate_msg(blk, mc.clone());
    }

    Ok(())
}

/// Computes the effects from user inputs on the tick state.
fn apply_player_input_changes(
    state: &mut TickState<'_>,
    input: &PlayerInput,
    _cfg: &SimConfig,
) -> Result<(), Error> {
    use action::ActionData;

    // Apply block changes.
    for act in input.block_actions() {
        match act {
            ActionData::PlaceBlock { pos, new_state } => {
                state.queue_set_block_state(*pos, &new_state);
            }

            ActionData::BreakBlock { pos } => {
                state.queue_set_block_state(*pos, &BlockStateSnapshot::new_simple(BlkId::zero()));
            }
        }
    }

    Ok(())
}

/// Executes a single block message delivery.
fn exec_single_msg(
    tick_state: &mut TickState,
    recip: BlkCoord,
    mc: &MessageContainer,
    cfg: &SimConfig,
) -> Result<(), Error> {
    let Some(id) = tick_state.get_block_id(recip) else {
        // For now, if we don't have it, just drop the message and print a warning.
        eprintln!("craftdig: dropping message at unloaded block {recip:?}");
        return Ok(());
    };

    // Skip delivering messages to air.
    if id.is_zero() {
        return Ok(());
    }

    let Some(blk) = cfg.registrar().get_block_tydata_by_id(id) else {
        // Skip delivering messages to blocks with unassigned IDs.  This doesn't make sense.
        eprintln!("craftdig: block with unknown ID {id:?} at {recip:?}",);
        return Ok(());
    };

    let ty = mc.ty();
    let Some(h) = blk.get_msg_handler_impl(ty) else {
        // Skip message if there's no handler for the message type.
        return Ok(());
    };

    // Set up the required context for the call.
    let wview = tick_state.world() as &dyn WorldView;
    let mut inst = BlockContext::new(recip, wview);

    // Actually invoke the handler.
    // TODO avoid clone here
    h.try_accept(&mut inst, mc.clone())?;

    // Now record the results.
    let blk_actions = inst.unpack();
    for act in blk_actions {
        tick_state.queue_set_block_state(act.target(), act.new_state());
    }

    Ok(())
}

/// Executes all message handlers for the current state.
fn exec_messages(tick_state: &mut TickState, mty: MsgTyId, cfg: &SimConfig) -> Result<(), Error> {
    eprintln!(
        "craftdig: processing messages of type {mty:?} in tick {}",
        tick_state.tick()
    );

    let msgs = tick_state.take_cur_tick_messages(mty);
    for (recip, mcs) in msgs.iter_entries() {
        for mc in mcs {
            exec_single_msg(tick_state, recip, mc, cfg)?;
        }
    }

    Ok(())
}

/// Applies a list of changes to blocks in the world.  This inserts messages for
/// the `onbreak` handlers, the `onplace` handlers, and `neighbor_update`
/// handlers as appropriate.
fn exec_block_changes(
    chgs: &[BlockChange],
    tick_state: &mut TickState,
    cfg: &SimConfig,
) -> Result<(), Error> {
    // TODO tweak all this to make it more deterministic

    // TODO Maybe make chunks be bitmaps and use btreemaps?  This is usually
    // going to be sparse so that should be very efficient.
    let mut changed_blocks = HashSet::new();

    // Go through each block we're changing and prepare messages to send to each
    // of them and their neighbors.
    for chg in chgs {
        let tgt = chg.target_pos();
        changed_blocks.insert(tgt);

        // Actually apply the change to the low-level world diff.
        let eworld = tick_state.world_mut();
        eworld.set_block_state(chg.target_pos(), chg.new_state());

        // FIXME make this less jank, maybe we should process the pre-break
        // before actually changing it?
        let br_mc = MessageContainer::new(cfg.msg_ids().onbreak, msgs::PreBreakMessage::new());
        let pl_mc = MessageContainer::new(cfg.msg_ids().onplace, msgs::OnPlaceMessage::new());

        tick_state.queue_immediate_msg(tgt, br_mc);
        tick_state.queue_immediate_msg(tgt, pl_mc);
    }

    // Actually change the blocks, while also preparing the neighbor update
    // messages for unchanged blocks too.  Blocks that were changed we don't
    // deliver neighbor update messages to since it could be weird.
    for chg in chgs {
        let tgt = chg.target_pos();

        for d in Direction::array() {
            let neighbor = tgt.neighbor_to(d);
            if !changed_blocks.contains(&neighbor) {
                let m = msgs::NeighborUpdateMessage::new(tgt);
                let nu_mc = MessageContainer::new(cfg.msg_ids().neighbor_update, m);
                tick_state.queue_immediate_msg(neighbor, nu_mc);
            }
        }
    }

    Ok(())
}

/// Processes an entity's physics update and produces a normal update for it.
///
/// Does *not* apply the change, just queues them in the tick state.
fn process_entity_phys(
    tick_state: &mut TickState,
    eid: &EntityId,
    cfg: &SimConfig,
) -> Result<(), Error> {
    let estate = tick_state
        .get_entity_state(eid)
        .expect("exec: tried to update unknown entity");

    let Some(epp) = EntityPhysState::try_from_entity_state(estate) else {
        return Ok(());
    };

    let world = tick_state.world();
    let phys_update =
        compute_entity_physics_update(&epp, None, world, cfg.registrar(), cfg.environ());

    eprintln!("entity {eid:?} phys update {phys_update:?}");

    if let Some(new_pos) = phys_update.new_pos() {
        tick_state.queue_entity_change(*eid, EntityUpdateData::MoveTo(new_pos));
    }

    if let Some(new_vel) = phys_update.new_vel() {
        tick_state.queue_entity_change(*eid, EntityUpdateData::ResetVelocity(new_vel));
    }

    // TODO handle grounded state

    Ok(())
}

fn exec_entity_phys(tick_state: &mut TickState, cfg: &SimConfig) -> Result<(), Error> {
    // FIXME hack but we only have the player to tick for now, figure out the loop later
    let ents = tick_state.world().get_entity_ids();
    for eid in ents {
        process_entity_phys(tick_state, &eid, cfg)?;
    }

    Ok(())
}

fn exec_entity_updates(tick_state: &mut TickState, cfg: &SimConfig) -> Result<(), Error> {
    let updates = tick_state.take_entity_updates();

    let world = tick_state.world_mut();

    for update in updates {
        let e = update.entity_id();

        let mut estate = world
            .get_entity_state(&e)
            .expect("exec: missing entity target")
            .clone();

        match update.data() {
            EntityUpdateData::MoveTo(pos) => {
                estate.set_position(*pos);
            }

            EntityUpdateData::AddVelocity(dv) => {
                if let Some(phys) = estate.phys_mut() {
                    phys.set_velocity(phys.velocity() + *dv);
                }
            }

            EntityUpdateData::ResetVelocity(vel) => {
                if let Some(phys) = estate.phys_mut() {
                    phys.set_velocity(*vel);
                }
            }
        }

        // Have to write it back afterwards.
        // TODO sort the updates by entity and then do chunks so that we can
        // apply all the updates to the same entity in a batch
        world.set_entity_state(e, estate);
    }

    Ok(())
}

fn generate_and_insert_chunk(
    cpos: ChunkCoord,
    tick_state: &mut TickState<'_>,
    cfg: &SimConfig,
) -> Result<(), Error> {
    eprintln!("craftdig: generating chunk at {cpos:?}");
    let chunk = cfg.chunk_provider().provide_chunk(cpos)?;
    tick_state.world_mut().set_chunk(cpos, chunk);
    Ok(())
}

fn generate_needed_chunks(tick_state: &mut TickState<'_>, cfg: &SimConfig) -> Result<(), Error> {
    let center_chunk = Vec3i::zero(); // TODO track the player

    let rad = cfg.world_gen_radius();
    let mut need_gen = Vec::new();

    for dcx in -rad..=rad {
        for dcy in -rad..=rad {
            for dcz in -rad..=rad {
                let check_cpos = ChunkCoord::from(center_chunk + Vec3i::new(dcx, dcy, dcz));

                let needs_gen = tick_state.world().get_chunk_ref(check_cpos).is_none();
                if needs_gen {
                    need_gen.push(check_cpos);
                }
            }
        }
    }

    for cpos in need_gen {
        generate_and_insert_chunk(cpos, tick_state, cfg)?;
    }

    Ok(())
}

fn exec_tick_main_phases(tick_state: &mut TickState<'_>, cfg: &SimConfig) -> Result<(), Error> {
    // Apply place messages and neighbor update messages.
    exec_messages(tick_state, cfg.msg_ids().onplace, cfg)?;
    exec_messages(tick_state, cfg.msg_ids().neighbor_update, cfg)?;

    // Apply block changes.
    let blk_changes = tick_state.take_block_changes();
    exec_block_changes(&blk_changes, tick_state, cfg)?;

    // Do entity physics.
    exec_entity_phys(tick_state, cfg)?;

    // Apply entity updates.
    exec_entity_updates(tick_state, cfg)?;

    // Apply onbreak messages.
    exec_messages(tick_state, cfg.msg_ids().onbreak, cfg)?;

    // Make sure we've created nearby chunks.
    generate_needed_chunks(tick_state, cfg)?;

    Ok(())
}

fn exec_tick(
    sim: &mut SimState,
    prev: &WorldState,
    input: &TickInput,
    cfg: &SimConfig,
) -> Result<(WorldState, editable::TickChanges), Error> {
    let tick_idx = sim.cur_tick();
    let init_tick_input = sim.take_next_tick();
    let mut tick_state = TickState::new(tick_idx, sim, prev);

    // Apply the stuff forwarded from the previous tick.
    apply_prev_next_tick(&mut tick_state, init_tick_input)?;

    // FIXME don't forcefully overwrite the origin chunk
    if tick_idx == 1 {
        generate_and_insert_chunk(ChunkCoord::from(Vec3i::zero()), &mut tick_state, cfg)?;
    }

    // Load any new chunks.
    for new_chunk in input.new_chunks() {
        let pos = new_chunk.coord();
        tick_state
            .world_mut()
            .set_chunk(pos, new_chunk.chunk().clone());
    }

    // Take any new players and create entities for them.
    for pj in input.player_joins() {
        eprintln!("creating entity for player {:?}", pj.id());

        let mut estate = EntityState::new_bare("Player".to_owned());
        estate.set_position(pj.location());
        estate.set_collider(make_player_aabb());
        *estate.phys_mut() = Some(PhysicsComponent::new_still(false));

        // Insert the player entity.
        tick_state
            .world_mut()
            .set_entity_state(pj.id().into(), estate);
    }

    // Take the inputs since the previous tick.
    for inp in input.player_inputs() {
        apply_player_input_changes(&mut tick_state, inp, cfg)?;
    }

    // Exec the main phases of the tick.
    exec_tick_main_phases(&mut tick_state, cfg)?;

    // Apply the changes to create the new world and return.
    tick_state.finalize_next_tick_state();
    let diff = tick_state.into_world_diff();
    let (new_state, chgs) = prev.apply_diff(diff);
    Ok((new_state, chgs))
}

fn exec_tick_loop(sstate: &mut SimState, io: &mut LoopIo, cfg: &SimConfig) -> Result<(), Error> {
    loop {
        // Remember the time so we can report how long things took.
        let ts_start = time::Instant::now();
        let next_ts_start = ts_start + cfg.tick_dur();

        // Prepare the pending inputs.
        let tick_inp = io.consume_ready_inputs();

        // Get the tick numbers and find out the state we're building on top of.
        let state = sstate.history().get_newest_state().clone();
        let new_cur_tick = sstate.increment_tick();

        // Actually go an execute the tick.
        let (new_state, chgs) = exec_tick(sstate, &state, &tick_inp, cfg)?;

        // Sometimes we should print how long the main tick exec took.
        let ts_exec_finish = time::Instant::now();
        let exec_dur = ts_exec_finish - ts_start;
        if cfg.tick_time_log_period() > 0 && new_cur_tick % cfg.tick_time_log_period() == 0 {
            eprintln!("craftdig: tick {new_cur_tick} finished, took {exec_dur:?}");
        }

        // Insert the new world state.
        let ws = Arc::new(new_state);
        if sstate
            .history_mut()
            .insert_tick(new_cur_tick, ws.clone())
            .is_some()
        {
            panic!("tick: overwrote state");
        }

        // Send the new tick update to the client handle.
        io.submit_new_tick_state(TickMsg::new(new_cur_tick, ws, chgs, Vec::new()));

        // Wait until it's time to loop around again.  This isn't perfect and
        // will give us some skew, but it's super simple so it's fine fow now.
        let ts_end = time::Instant::now();
        thread::sleep(next_ts_start - ts_end);
    }
}

pub fn tick_loop_main(init_world: Arc<WorldState>, mut io: LoopIo, cfg: SimConfig) {
    let mut sstate = SimState::new(history::History::create(init_world));

    if let Err(e) = exec_tick_loop(&mut sstate, &mut io, &cfg) {
        eprintln!("TICK LOOP ERROR: {e:?}");
    }
}
